package router

import (
	"context"
	"gitee.com/aldrice/monitor/configs"
	"gitee.com/aldrice/monitor/ent"
	"gitee.com/aldrice/monitor/internal/registry"
	"gitee.com/aldrice/monitor/pkg/logging"
	"github.com/labstack/echo/v4"
	"go.uber.org/zap"
	"net/http"
	"strconv"
)

type Context struct {
	echo.Context
	uid    int
	ctx    context.Context
	logger *zap.Logger
}

// UID return user's id (always > 0),
// If not authorized it's 0
func (c *Context) UID() int {
	return c.uid
}

func (c *Context) SetUID(uid int) {
	c.uid = uid
}

func (c *Context) MyLogger() *zap.Logger {
	return c.logger
}

// stdResult only use for Context.StdResult
type stdResult struct {
	Code int         `json:"code"`
	Data interface{} `json:"data"`
}

// ApiSuccess writes api success result to http response.
// Meaning this call is processed in expected.
// Auto set Http Status = 200, Response Code = 0.
func (c *Context) ApiSuccess(data interface{}) error {
	return c.JSON(http.StatusOK, &stdResult{Code: 0, Data: data})
}

// StdResult writes standard success result to http response.
// Meaning this call is processed in expected.
// Auto set Http Status = 200, Response Code = 0.
func (c *Context) StdResult(data interface{}) error {
	return c.JSON(http.StatusOK, &stdResult{Code: 0, Data: data})
}

// StdTable writes standard table result to http response
func (c *Context) StdTable(total int, items interface{}) error {
	return c.StdResult(&tableResult{items, total})
}

// ApiError writes api error result to http response.
// Meaning this call is failure with user's problem.
// Auto set Http Status = 422.
func (c *Context) ApiError(code int, msg string) error {
	c.logger.Warn("api return error", zap.Int("code", code), zap.String("msg", msg))
	return c.JSON(422, &apiError{Code: code, Msg: msg})
}

// PrepApiError write prepared api error to http response
func (c *Context) PrepApiError(e *apiError) error {
	c.logger.Warn("api return error", zap.Error(e))
	return c.JSON(422, e)
}

// ApiException writes api error result to http response.
// Meaning this call is failure with server's problem.
func (c *Context) ApiException(httpCode int, code int, msg string) error {
	c.logger.Warn("api return exception", zap.Int("code", code), zap.String("msg", msg))
	return c.JSON(httpCode, &apiError{Code: code, Msg: msg})
}

// PrepApiException write prepared api exception to http response
func (c *Context) PrepApiException(e *apiException) error {
	c.logger.Warn("api return exception", zap.Error(e))
	return c.JSON(e.HttpCode, &e.Internal)
}

// tableResult only use for Context.ApiTable
type tableResult struct {
	Items interface{} `json:"items"`
	Total int         `json:"total"`
}

// ApiTable writes api table result
// only helper for ApiSuccess
func (c *Context) ApiTable(total int, items interface{}) error {
	return c.JSON(http.StatusOK, &tableResult{Total: total, Items: items})
}

const DefaultPageSize int = 10

// DeterminePage return requested table parameters,
// determine by query params: page, page_size
func (c *Context) DeterminePage() (offset int, pageSize int) {
	page, err := strconv.Atoi(c.QueryParam("page"))
	if err != nil {
		page = 1
	}
	if page < 1 {
		page = 1
	}
	pageSize, err = strconv.Atoi(c.QueryParam("page_size"))
	if err != nil {
		pageSize = DefaultPageSize
	}
	offset = (page - 1) * pageSize
	return
}

// DetermineOrder return requested table parameters,
// determine by query params: order_by, order
func (c *Context) DetermineOrder(allow []string) ent.OrderFunc {
	req := c.QueryParam("order_by")
	key := "id"
	for _, item := range allow {
		if item == req {
			key = req
			break
		}
	}
	if c.QueryParam("order") == "asc" {
		return ent.Asc(key)
	}
	return ent.Desc(key)
}

// DB return an Ent client to access database
func (c *Context) DB() *ent.Client {
	return registry.Ent()
}

// Tx execute a DB Transaction
func (c *Context) Tx(f func(*ent.Tx, context.Context) error) error {
	tx, err := registry.Ent().Tx(c.ctx)
	if err != nil {
		return err
	}
	err = f(tx, c.ctx)
	if err != nil {
		if err2 := tx.Rollback(); err2 != nil {
			return err2
		}
		return err
	}
	return tx.Commit()
}

// Ctx return a context.Context with timeout & http connection
func (c *Context) Ctx() context.Context {
	return c.ctx
}

func WrapContext(next echo.HandlerFunc) echo.HandlerFunc {
	return func(ec echo.Context) error {
		var ctx = ec.Request().Context()
		if configs.Config.IsProd {
			var cancel context.CancelFunc
			ctx, cancel = context.WithTimeout(ctx, configs.Config.ApiTimeout)
			defer cancel()
		}
		return next(&Context{
			Context: ec,
			ctx:     ctx,
			logger:  logging.Logger().With(zap.String("uri", ec.Request().RequestURI)),
		})
	}
}

type HandlerFunc func(c *Context) error

func WrapHandler(h HandlerFunc) echo.HandlerFunc {
	return func(ec echo.Context) error {
		return h(ec.(*Context))
	}
}
